use std::any::Any;
use std::marker::PhantomData;

use hashbrown::HashMap;
use xactor::{Actor, Addr, Context, Handler, Message, Result, Service};

#[derive(Default)]
pub struct ActorCenter {
    id_name: HashMap<u64, String>,
    name_actor: HashMap<String, Box<dyn Any + Send>>,
}

impl Service for ActorCenter {}

impl Actor for ActorCenter {
    fn name(&self) -> &str {
        "ActorCenter"
    }
}

pub struct Register<T> {
    pub name: String,
    pub addr: Addr<T>,
}

impl<T: 'static + Actor> Message for Register<T> {
    type Result = ();
}

#[async_trait::async_trait]
impl<T: 'static + Actor> Handler<Register<T>> for ActorCenter {
    async fn handle(&mut self, _ctx: &mut Context<Self>, msg: Register<T>) -> Result<()> {
        let actor_id = msg.addr.actor_id();
        assert!(self.id_name.insert(actor_id, msg.name.clone()).is_none());
        assert!(self
            .name_actor
            .insert(msg.name, Box::new(msg.addr))
            .is_none());
        Ok(())
    }
}

pub struct Unregister(pub u64);

impl Message for Unregister {
    type Result = ();
}

#[async_trait::async_trait]
impl Handler<Unregister> for ActorCenter {
    async fn handle(&mut self, _ctx: &mut Context<Self>, msg: Unregister) -> Result<()> {
        if let Some(name) = self.id_name.remove(&msg.0) {
            self.name_actor.remove(&name);
        }
        Ok(())
    }
}

pub struct Query<T: Actor>(String, PhantomData<T>);

impl<T: Actor> Query<T> {
    pub fn new(name: &str) -> Self {
        Self(name.into(), PhantomData)
    }
}

impl<T: Actor> Message for Query<T> {
    type Result = Addr<T>;
}

#[async_trait::async_trait]
impl<T: Actor> Handler<Query<T>> for ActorCenter {
    async fn handle(&mut self, _ctx: &mut Context<Self>, msg: Query<T>) -> Result<Addr<T>> {
        self.name_actor
            .get(&msg.0)
            .and_then(|addr| addr.downcast_ref::<Addr<T>>())
            .cloned()
            .ok_or_else(|| anyhow!("actor_center query actor:{} not found", msg.0))
    }
}
